Когда вы пишете просто на React — то используете Redux store как глобальное хранилище — ничего сложного.
Но когда начинаете задумываться о том, чтобы использовать Server-side Rendering — то по началу может возникать некоторая путаница с непривычки.
В React — результаты запросов сохраняем в Redux store — и уже на основании этих данных рендерится страница — всё понятно.
В Next.js же — страница отрендерилась на сервере — и пришла уже в виде html и css. Внимание вопрос: как тогда использовать Redux — если код страницы нам уже пришёл? И для чего вообще в таком случае нужен Redux при использовании Next?
Работает это примерно так: страница рендерится на сервере. Когда пользователь заходит на сайт — он скачивает эту страницу с сервера. На этом этапе серверный рендеринг закончился. Пользователь получил страницу в базовом виде — таком, как её видит весь интернет и роботы поисковиков. В этот момент в Redux store — хранятся исключительно те значения, какие там были при инициализации.
Если после этого сделать запрос к серверу и изменить значения в store — они там сохранятся. И если все ссылки для переходов по страницам сайта были обёрнуты в тег <Link></Link> — то при переходе по ним приложение будет вести себя в плане Redux — как SPA — всё, что загружено в Redux store — останется без изменений.
Например, переходим на главную страницу сайта. Получили эту страницу отрендереной с сервера. После чего залогинились — и информацию о пользователе (например, его имя) — сохранили в Redux. Тогда когда начнёте переходить на другие страницы сайта — его имя уже будет храниться в store и не придётся его каждый раз заново запрашивать.
При всём этом есть один интересный сценарий использования — как можно сочетать серверный рендеринг со store. Рассмотрим на примере:
Допустим, у нас 2 глобальных свойства приходят с бекенда:
-
язык сайта
-
идут сейчас технические работы или нет
При этом, если пользователь изменил язык сайта — мы хотим чтобы это сохранилось для всех страниц сайта (допустим, пользователь ещё не разрешил хранит кукисы).
Примерно так будет выглядеть Redux store:
Типы
export interface IGlobalSettings{ isTechnicalWork: boolean, language: string, }
globalSettingsReducer.ts
import {IGlobalSettings} from "@/types/redux_types"; import {createSlice, PayloadAction} from "@reduxjs/toolkit"; export const initialState: IGlobalSettings = { isTechnicalWork: false, language: "default", } export const GlobalSettingsSlice = createSlice({ name: 'global_settings', initialState, reducers: { GlobalUpdateLanguage (state, action: PayloadAction<string>){ state.language = action.payload; }, GlobalUpdateTechWork (state, action: PayloadAction<boolean>){ state.isTechnicalWork = action.payload; }, } }) export default GlobalSettingsSlice.reducer;
store.ts
import {configureStore, combineReducers} from "@reduxjs/toolkit"; import { createWrapper } from 'next-redux-wrapper'; import globalSettingsReducer from "@/redux/globalSettingsReducer"; const rootReducer = combineReducers({ global_settings: globalSettingsReducer, //другие редюсеры добавлять сюда. }) export const setupStore = () => { return configureStore({ reducer: rootReducer }) } export const wrapper = createWrapper(configureStore); export type RootState = ReturnType<typeof rootReducer> export type AppStore = ReturnType<typeof setupStore> export type AppDispatch = AppStore['dispatch']
Определим хуки, чтобы было удобнее работать:
хуки
import {useDispatch, useSelector, TypedUseSelectorHook} from "react-redux"; import {AppDispatch, RootState} from "@/redux/store"; export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Теперь перейдём к коду тестовой страницы, на которой мы будем всё это использовать:
import {GetStaticProps} from "next"; import axios from "axios"; import {IGlobalSettings} from "@/types/redux_types"; import React, {FC, ReactNode, useEffect} from "react"; import Head from "next/head"; import {useAppDispatch, useAppSelector} from "@/hooks/redux"; import {GlobalSettingsSlice} from "@/redux/globalSettingsReducer"; export type GlobalSettingsProps = { p_global_settings: IGlobalSettings, } const TestPage:FC<GlobalSettingsProps> = ({p_global_settings}) => { const dispatch = useAppDispatch() const state_language = useAppSelector(state => state.global_settings.language) const state_tech_works = useAppSelector(state => state.global_settings.isTechnicalWork) let tech_works_string :string | ReactNode if (state_language == "EN") {tech_works_string = <h1>Technical works!</h1>} if (state_language == "RU") {tech_works_string = <h1>Технические работы</h1>} let main_string :string | ReactNode if (state_language == "EN") {main_string = <h1>Test!</h1>} if (state_language == "RU") {main_string = <h1>Тест!</h1>} useEffect(() => { if (state_language == "default") { dispatch(GlobalSettingsSlice.actions.GlobalUpdateLanguage(p_global_settings.language)) //Обновляем язык - на полученный от сервера - если его пользователь сам не менял } dispatch(GlobalSettingsSlice.actions.GlobalUpdateTechWork(p_global_settings.isTechnicalWork)) //Обновляем статус технических работ. },[]); return ( <> <Head> <title>Test page</title> <meta name="description" content="Test page" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> {state_tech_works ? tech_works_string : main_string} </main> </> ) } export default TestPage; export const getStaticProps: GetStaticProps = async () => { const response = await axios.get<IGlobalSettings>('https://api.somesite.com/global_settings') const test: IGlobalSettings = { isTechnicalWork: false, language: "EN", } return { props: { p_global_settings: response.data //test //Для тестирования без api - можно заменить response.data на test //В переменную isTechnicalWork получаем false, //В переменную language получаем "EN" - по умолчанию английский язык }, revalidate: 60, } }
В функции «getStaticProps» — происходит получение данных с сервера. Эта информация обновляется раз в 60 секунд — т.е. Next будет раз в минуту опрашивать сервер — что поменялось.
Полученные от сервера данные мы передаём в компоненту страницы — «TestPage».
Потом через хук useEffect мы эти данные передаём в Redux store — 1 раз после загрузки страницы.
Таким образом, у всех пользователей в store будут после загрузки страницы не те данные, которыми инициируюется store — а те данные, которые раз в минуту приходят от бекенда и рендерятся в статической странице.
Сначала мы обновляем данные в store из static props, а потом уже из store берём информацию для рендеринга той страницы, которую увидит пользователь. При изменении значений в state — будет меняться и страница.
При этом, если пользователь поменяет язык — то при переходе на другие страницы эту настройку в state не затрёт на те данные, которые от бекенда приходят в getStaticProps.
В результате, если у нас на страницу заходят 100 пользователей в минуту — то за минуту у нас будет всего одно обращение к бекенду вместо 100. Но и если никто не зайдёт — будет всё то же 1 обращение к бекенду в минуту. (тут мы говорим не про запрос личных данных пользователя — а про данные, которые одинаковые для всех пользователей. Имя пользователя придётся запрашивать всё те же 100 раз).
Таким образом, используя Next.js — можно инициировать Redux данными с бекенда, не делая каждый раз запрос к бекенду для каждого пользователя по поводу общих данных (которые одинаковые для всех пользователей) — и таким образом ощутимо уменьшить нагрузку на бекенд.
P.S: ищу удалёнку — контакты в профиле.
ссылка на оригинал статьи https://habr.com/ru/articles/737666/